﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using LGPeWJNM.BaseClasses;

namespace BatchDesvPad
{
    public class Processor
    {
        public static void Routine(object input)
        {
            var inp = (ThreadInput)input;

            var medianaProcessor = MedianaProcessor();
            var clusterizacaoProcessor = ClusterizacaoProcessor();
            var roteirizacaoProcessor = RoteirizacaoProcessor();
            var otimizacaoProcessor = OtimizacaoProcessor();

            System.Diagnostics.Stopwatch pathStopwatch = new System.Diagnostics.Stopwatch(),
                resultadoStopwatch = new System.Diagnostics.Stopwatch(),
                medianaStopwatch = new System.Diagnostics.Stopwatch(),
                clusterizacaoStopwatch = new System.Diagnostics.Stopwatch(),
                roteirizacaoStopwatch = new System.Diagnostics.Stopwatch(),
                otimizacaoStopwatch = new System.Diagnostics.Stopwatch(),
                saveStopwatch = new System.Diagnostics.Stopwatch();

            var xsResultado = new System.Xml.Serialization.XmlSerializer(typeof(LGPeWJNM.DataAccessLayer.Resultado));
            System.Xml.XmlWriter xwResultadoCompleto = null, xwResultadoResumido = null;
            var xwResultados = new List<System.Xml.XmlWriter>();
            if (!string.IsNullOrEmpty(inp.ResultadoCompletoPath))
            {
                xwResultadoCompleto = System.Xml.XmlWriter.Create(inp.ResultadoCompletoPath);
                xwResultados.Add(xwResultadoCompleto);
            }
            if (!string.IsNullOrEmpty(inp.ResultadoResumidoPath))
            {
                xwResultadoResumido = System.Xml.XmlWriter.Create(inp.ResultadoResumidoPath);
                xwResultados.Add(xwResultadoResumido);
            }
            foreach (var xw in xwResultados)
            {
                xw.WriteStartDocument();
                xw.WriteStartElement("PRVC");
            }

            try
            {
                foreach (var instancia in inp.Instancias)
                {
                    foreach (var xw in xwResultados)
                    {
                        xw.WriteStartElement("Instancia");
                        xw.WriteAttributeString("Classe", instancia.Key.Classe);
                        xw.WriteAttributeString("Id", instancia.Key.Instancia);
                        xw.WriteAttributeString("Otimo", instancia.Value.Otimo.ToString());
                    }
                    foreach (var flowPath in inp.FlowPaths)
                    {
                        int bksIndex = 0;
                        double bksValue = double.MaxValue;
                        var xPath = new XDocument();
                        xPath.Add(new XElement("Path"));
                        ((XElement)xPath.FirstNode).Add(new XAttribute("Algoritmos", flowPath.ToString()));

                        pathStopwatch.Reset();
                        pathStopwatch.Start();

                        try
                        {
                            for (int i = 0; (inp.IteracaoStop.HasValue && inp.IteracaoStop > i) ||
                                        (inp.BksStop.HasValue && inp.BksStop > i - bksIndex); i++)
                            {
                                Exception exception = null;
                                var resultado = new LGPeWJNM.DataAccessLayer.Resultado();

                                clusterizacaoStopwatch.Reset();
                                medianaStopwatch.Reset();
                                otimizacaoStopwatch.Reset();
                                resultadoStopwatch.Reset();
                                roteirizacaoStopwatch.Reset();
                                saveStopwatch.Reset();

                                resultadoStopwatch.Start();
                                try
                                {
                                    medianaStopwatch.Start();
                                    resultado.Mediana = medianaProcessor[(byte)flowPath.Mediana].Processar(instancia.Value.Destinos, instancia.Value.QuantidadeVeiculos, instancia.Value.CapacidadeVeiculo);
                                    medianaStopwatch.Stop();
                                    clusterizacaoStopwatch.Start();
                                    resultado.Clusterizacao = clusterizacaoProcessor[(byte)flowPath.Clusterizacao].Processar(resultado.Mediana, instancia.Value.Destinos, instancia.Value.Deposito, instancia.Value.CapacidadeVeiculo);
                                    clusterizacaoStopwatch.Stop();
                                    roteirizacaoStopwatch.Start();
                                    resultado.Roteirizacao = roteirizacaoProcessor[(byte)flowPath.Roteirizacao].Processar(resultado.Clusterizacao, instancia.Value.Deposito, instancia.Value.QuantidadeVeiculos, instancia.Value.CapacidadeVeiculo);
                                    roteirizacaoStopwatch.Stop();
                                    otimizacaoStopwatch.Start();
                                    resultado.Otimizacao = otimizacaoProcessor[(byte)flowPath.Otimizacao].Processar(resultado.Roteirizacao, instancia.Value.Deposito, instancia.Value.QuantidadeVeiculos, instancia.Value.CapacidadeVeiculo);
                                    otimizacaoStopwatch.Stop();
                                }
                                catch (Exception ex)
                                {
                                    if (ex is System.Threading.ThreadAbortException)
                                        throw ex;
                                    exception = ex;
                                }
                                finally
                                {
                                    resultadoStopwatch.Stop();
                                    saveStopwatch.Start();
                                    var xResultado = xsResultado.ResultadoToXml(resultado, resultadoStopwatch, medianaStopwatch, clusterizacaoStopwatch, roteirizacaoStopwatch, otimizacaoStopwatch, saveStopwatch, exception);
                                    ((XElement)xPath.FirstNode).Add(xResultado);
                                }

                                if (resultado.Otimizacao != null && bksValue > resultado.Otimizacao.CustoTotal())
                                {
                                    bksValue = resultado.Otimizacao.CustoTotal();
                                    bksIndex = i;
                                    if (FoundNewBks != null)
                                        FoundNewBks();
                                }
                                if (CompletedIteraction != null)
                                    CompletedIteraction();
                            }
                        }
                        finally
                        {
                            pathStopwatch.Stop();
                            ((XElement)xPath.FirstNode).Add(new XAttribute("Tempo", pathStopwatch.ElapsedMilliseconds),
                                new XAttribute("Ticks", pathStopwatch.ElapsedTicks));

                            if (xwResultadoCompleto != null)
                                ((XElement)xPath.FirstNode).WriteTo(xwResultadoCompleto);
                            foreach (var resultadoIter in xPath.Element("Path").Elements("Resultado"))
                            {
                                if (resultadoIter.Element("Mediana") != null)
                                    resultadoIter.Element("Mediana").RemoveNodes();
                                if (resultadoIter.Element("Clusterizacao") != null)
                                    resultadoIter.Element("Clusterizacao").RemoveNodes();
                                if (resultadoIter.Element("Roteirizacao") != null)
                                    resultadoIter.Element("Roteirizacao").RemoveNodes();
                                if (resultadoIter.Element("Otimizacao") != null)
                                    resultadoIter.Element("Otimizacao").RemoveNodes();
                            }
                            if (xwResultadoResumido != null)
                                ((XElement)xPath.FirstNode).WriteTo(xwResultadoResumido);
                        }
                        if (CompletedPath != null)
                            CompletedPath();
                    }
                    foreach (var xw in xwResultados)
                        xw.WriteEndElement();

                    if (CompletedInstance != null)
                        CompletedInstance();
                }
            }
            catch (System.Threading.ThreadAbortException)
            {
                return;
            }
            finally
            {
                foreach (var xw in xwResultados)
                {
                    xw.WriteEndDocument();
                    xw.Close();
                }
                if (CompletedRoutine != null)
                    CompletedRoutine();
            }
        }

        public delegate void Completed();

        public static event Completed CompletedInstance;

        public static event Completed CompletedPath;

        public static event Completed CompletedIteraction;

        public static event Completed CompletedRoutine;

        public static event Completed FoundNewBks;

        #region processors initializers

        private static Dictionary<byte, LGPeWJNM.BaseClasses.IRoteirizacao> OtimizacaoProcessor()
        {
            var otimizacaoProcessor = new Dictionary<byte, LGPeWJNM.BaseClasses.IRoteirizacao>();
            otimizacaoProcessor.Add((byte)Flow.Otimizacao._2Opt, new LGPeWJNM._2Opt._2Opt());
            otimizacaoProcessor.Add((byte)Flow.Otimizacao.NenhumaOtimizacao, new dummyProcessor());
            return otimizacaoProcessor;
        }

        private static Dictionary<byte, LGPeWJNM.BaseClasses.IRoteirizacao> RoteirizacaoProcessor()
        {
            var roteirizacaoProcessor = new Dictionary<byte, LGPeWJNM.BaseClasses.IRoteirizacao>();
            roteirizacaoProcessor.Add((byte)Flow.Roteirizacao.Vmp, new LGPeWJNM.Vmp.Vmp());
            roteirizacaoProcessor.Add((byte)Flow.Roteirizacao.Ime, new LGPeWJNM.Ime.Ime());
            roteirizacaoProcessor.Add((byte)Flow.Roteirizacao.NenhumaRoteirizacao, new dummyProcessor());
            return roteirizacaoProcessor;
        }

        private static Dictionary<byte, LGPeWJNM.BaseClasses.IClusterizacao> ClusterizacaoProcessor()
        {
            var clusterizacaoProcessor = new Dictionary<byte, LGPeWJNM.BaseClasses.IClusterizacao>();
            clusterizacaoProcessor.Add((byte)Flow.Clusterizacao.GilletJohnson, new LGPeWJNM.GilletJohnson.GilletJohnson());
            clusterizacaoProcessor.Add((byte)Flow.Clusterizacao.Trunfo, new LGPeWJNM.Trunfo.Trunfo());
            return clusterizacaoProcessor;
        }

        private static Dictionary<byte, LGPeWJNM.BaseClasses.IPMediana> MedianaProcessor()
        {
            var medianaProcessor = new Dictionary<byte, LGPeWJNM.BaseClasses.IPMediana>();
            medianaProcessor.Add((byte)Flow.Mediana.TeitzEBart, new LGPeWJNM.TeitzEBart.TeitzEBart());
            medianaProcessor.Add((byte)Flow.Mediana.MonteCarlo, new LGPeWJNM.MonteCarlo.MonteCarlo());
            return medianaProcessor;
        }

        private class dummyProcessor : LGPeWJNM.BaseClasses.IRoteirizacao
        {
            public IEnumerable<LinkedList<LGPeWJNM.BaseClasses.ICliente>> Processar(IEnumerable<IEnumerable<LGPeWJNM.BaseClasses.ICliente>> destinos, LGPeWJNM.BaseClasses.Point deposito, int quantidadeVeiculos, float capacidadeVeiculo)
            {
                if (destinos.First().First().Demanda != 0)
                {
                    return destinos.Select(s =>
                    {
                        var ret = s is LinkedList<LGPeWJNM.BaseClasses.ICliente> ? (LinkedList<LGPeWJNM.BaseClasses.ICliente>)s : new LinkedList<LGPeWJNM.BaseClasses.ICliente>(s);
                        ret.AddFirst(new Cliente()
                        {
                            Id = 1,
                            Coordenada = deposito,
                            Demanda = 0
                        });
                        ret.AddLast(new Cliente()
                        {
                            Id = 1,
                            Coordenada = deposito,
                            Demanda = 0
                        });
                        return ret;
                    });
                }
                return destinos.Select(s => new LinkedList<LGPeWJNM.BaseClasses.ICliente>(s));
            }
        }

        #endregion processors initializers

        public struct ThreadInput
        {
            public string ResultadoCompletoPath { get; set; }

            public string ResultadoResumidoPath { get; set; }

            public IEnumerable<Flow.FlowPath> FlowPaths { get; set; }

            public int? IteracaoStop { get; set; }

            public int? BksStop { get; set; }

            public IDictionary<LGPeWJNM.DataAccessLayer.IdInstancia, LGPeWJNM.DataAccessLayer.Instancia> Instancias { get; set; }
        }
    }
}